/*
 *               ____ _________  _              _
 *   _ __  _ __ | ___|___ /___ \| |_ ___   ___ | |
 *  | '_ \| '_ \|___ \ |_ \ __) | __/ _ \ / _ \| |
 *  | |_) | | | |___) |__) / __/| || (_) | (_) | |
 *  | .__/|_| |_|____/____/_____|\__\___/ \___/|_|
 *  |_|
 *         by hakanai
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 */

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdint.h>
#include <time.h>
#include <stdbool.h>

#define TOOL_VER "v0.0.1"
/*#define DEFAULT_PORT "/tmp/sss"*/
#define DEFAULT_PORT "/dev/ttyUSB0"

#define PN532_DIAGNOSE_CMD 0x04
#define PN532_VERSION_CMD 0x02

#ifndef ESP_OK
#define ESP_OK 0
#define ESP_FAIL -1
#endif

static const char *TAG = "PN532-DRV";
static bool _print_hex;

#ifndef ESP_LOGE
#define ESP_LOGE(t, ...) do { \
  fprintf(stderr, "[%s] ", t); \
  fprintf(stderr,  __VA_ARGS__ ); \
  fprintf(stderr, "\n"); \
} while(0)
#endif

enum pn532_init_state_enum {
  PN532_STATE_READ_RFSTATUS = 0,
  PN532_STATE_READ_VERSION,
  PN532_STATE_GET_STATUS,
  PN532_STATE_SET_SCANNING,
  PN532_STATE_COMPLETE
};

// {{{ set_port_opt()
static void set_portopt(int fd) {
  struct termios options;

/*  tcgetattr(fd, &options);*/
  memset(&options, 0, sizeof(struct termios));

  cfsetispeed(&options, B115200);
  cfsetospeed(&options, B115200);

  /* Set 8 bit */
  options.c_cflag &= ~CSIZE;
  options.c_cflag |= CS8;
  options.c_cflag |= (CLOCAL | CREAD);

  /* Set 1 stop */
  options.c_cflag &= ~CSTOPB;
  options.c_cflag &= ~CRTSCTS;

  /* Set no parity */
  options.c_cflag &= ~(PARENB | PARODD);
  options.c_iflag &= ~INPCK;
  options.c_iflag &= ~IGNBRK;

  /* Raw input */
  options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

  /* Software flow control is disabled */
  options.c_iflag &= ~(IXON | IXOFF | IXANY);

  /* Raw ouput */
  options.c_oflag &=~ OPOST;

  /* Unused because we use open with the NDELAY option */
  options.c_cc[VMIN] = 0;
  options.c_cc[VTIME] = 0;

  if (tcsetattr(fd, TCSANOW, &options) != 0) {
/*    printf("Unable to set port options\n");*/
/*    exit(1);*/
  }
}
// }}}

static void printbuf(uint8_t *buffer, uint16_t len) {
  for(uint16_t i = 0; i < len; i++){
    printf("%.2x ", buffer[i]);
  }
}

#define pprint(a, b, c) if (_print_hex) { printf("%s: ", a); printbuf(b, c); puts(""); }

#define MAXBUFSIZ 256
static int send_packet(int fd, const uint8_t *buffer, uint16_t len) {
  uint8_t cmd[MAXBUFSIZ] = { 0 };
  uint8_t sum = 0xd4;
  int i;

  if (len > MAXBUFSIZ || len < 1) {
    ESP_LOGE(TAG, "data size is wrong %d", len);
    return ESP_FAIL;
  }

  /* Make header */
  for (i = 0; i < 9; i++)
    cmd[i] = 0xff;

  /* Padding */
  cmd[i++] = 0x00;
  cmd[i++] = 0xff;

  /* Set length */
  cmd[i++] = len + 1;
  cmd[i++] = -(len + 1);

  /* Set target id */
  cmd[i++] = 0xd4;

  for (int j = 0; j < len; j++) {
    cmd[i++] = buffer[j];
    sum += buffer[j];
  }

  cmd[i++] = -(sum);
  cmd[i++] = 0xd4;

  if (write(fd, cmd, i) < i)
    return ESP_FAIL;

  pprint("send", cmd, i);

  return ESP_OK;
}

static int read_buffer(int fd, uint8_t *buffer, int len) {
  int rlen = 0;
  while ((rlen = read(fd, buffer, len)) < 1)
    usleep(10);

  return rlen;
}

static int recv_packet(int fd, uint8_t *buffer) {
  int len, j, i;
  const uint8_t PN532_ACK[] = {0, 0, 0xFF, 0, 0xFF, 0, 0, 0, 0xFF};
  uint8_t dlen[2];
  uint8_t sum;

  len = read_buffer(fd, buffer, MAXBUFSIZ);

  pprint("recv", buffer, len);

  i = sizeof(PN532_ACK);

  if (memcmp(buffer, PN532_ACK, i)) {
/*    ESP_LOGE(TAG, "Invalid frame");*/
    return ESP_FAIL;
  }

  dlen[0] = buffer[i++];
  dlen[1] = buffer[i++];

  if (len < 2 || (uint8_t)(dlen[0] + dlen[1]) != 0) {
/*    ESP_LOGE(TAG, "Incorrect length");*/
    return ESP_FAIL;
  }

  if (buffer[i++] != 0xd5) {
/*    ESP_LOGE(TAG, "Hostcheck failed");*/
    return ESP_FAIL;
  }

  dlen[0] -= 2;
  sum = 0xd5 + buffer[i++];

  for (j = 0; j < dlen[0]; j++) {
    sum += buffer[i + j];
  }

  if (((uint8_t)(sum + buffer[len - 2])) != 0 || buffer[len - 1] != 0) {
/*    ESP_LOGE(TAG, "Checksum error %d %d %d", buffer[len-1], sum, (uint8_t)(sum + buffer[len-1]));*/
    return ESP_FAIL;
  }

  for (j = 0; j < dlen[0]; j++) {
    buffer[j] = buffer[i++];
  }

  return dlen[0];
}

static int read_status(int fd) {
  const uint8_t cmd[2] = { 0x58, 0x00 };
  uint8_t buffer[MAXBUFSIZ] = { 0 };
  const uint8_t PN532_ACK[] = {0, 0, 0xFF, 0, 0xFF, 0};
  const uint8_t ack_len = sizeof(PN532_ACK);

  if (send_packet(fd, cmd, 2) != ESP_OK) {
    return ESP_FAIL;
  }

  if (read_buffer(fd, buffer, MAXBUFSIZ) != ack_len) {
    return ESP_FAIL;
  }

  if (memcmp(buffer, PN532_ACK, ack_len)) {
    return ESP_FAIL;
  }

  pprint("[ack]", buffer, ack_len);

  return ESP_OK;
}

static int read_diagnose(int fd) {
  const uint8_t cmd[1] = { PN532_DIAGNOSE_CMD };
  uint8_t buffer[MAXBUFSIZ] = { 0 };
  int rlen;

  if (send_packet(fd, cmd, 1) != ESP_OK) {
    return ESP_FAIL;
  }

  if ((rlen = recv_packet(fd, buffer)) < 1) {
    return ESP_FAIL;
  }

  return ESP_OK;
}

static int read_version(int fd) {
  const uint8_t cmd[1] = { PN532_VERSION_CMD };
  uint8_t buffer[MAXBUFSIZ] = { 0 };
  int rlen;

  if (send_packet(fd, cmd, 1) != ESP_OK) {
    return ESP_FAIL;
  }

  if ((rlen = recv_packet(fd, buffer)) < 3) {
    return ESP_FAIL;
  }

  printf("PN532 ver %d.%d, features: %d\n",
      buffer[1], buffer[2], buffer[3]);

  return ESP_OK;
}

static int set_scanning(int fd) {
  const uint8_t cmd[5] = { 0x32, 0x05, 0xff, 0x01, 0x10 };
  uint8_t buffer[MAXBUFSIZ] = { 0 };
  int rlen;

  if (send_packet(fd, cmd, 5) != ESP_OK) {
    return ESP_FAIL;
  }

  if ((rlen = recv_packet(fd, buffer)) != 0) {
    return ESP_FAIL;
  }

  printf("PN532 mode set\n");
  return ESP_OK;
}


static void pn532_init(int fd) {
  static int test_state;

  while (test_state != PN532_STATE_COMPLETE) {
    switch (test_state) {
      case PN532_STATE_READ_RFSTATUS:
        if (read_status(fd) == ESP_OK)
          test_state = PN532_STATE_READ_VERSION;
        break;
      case PN532_STATE_READ_VERSION:
        if (read_version(fd) == ESP_OK)
          test_state = PN532_STATE_GET_STATUS;
        break;
      case PN532_STATE_GET_STATUS:
        if (read_diagnose(fd) == ESP_OK)
          test_state = PN532_STATE_SET_SCANNING;
        break;
      case PN532_STATE_SET_SCANNING:
        if (set_scanning(fd) == ESP_OK)
          test_state = PN532_STATE_COMPLETE;
        break;
    }
  }
}

static int read_passive(int fd) {
  const uint8_t cmd[3] = { 0x4a, 0x02, 0x00 };
  uint8_t buffer[MAXBUFSIZ] = { 0 };
  int rlen;

  if (send_packet(fd, cmd, 3) != ESP_OK) {
    return ESP_FAIL;
  }

  if ((rlen = recv_packet(fd, buffer)) < 1) {
    return ESP_FAIL;
  }

  if (buffer[0] == 0x00) { // (No card) skip
    return ESP_FAIL;
  }

  printf("Read (%d) bytes:", rlen);
  printbuf(buffer, rlen);
  puts("");

  return ESP_OK;
}

static void usage(char *app) {
  printf("Usage: %s [options]\n\n", app);
  printf(" -p[port]\t\ttty port (default: /dev/ttyUSB0)\n");
  printf(" -x\t\t\tprint hex (debug)\n");
  printf("\n");
  printf("pn532tool %s by @hakanai\n", TOOL_VER);
  exit(1);
}

int main(int ac, char **av) {
  char *port = DEFAULT_PORT;
  int fd;
  int c;

  while ((c = getopt(ac, av, "p:xhv")) != -1) {
    switch (c) {
      case 'p':
        port = strdup(optarg);
        break;
      case 'x':
        _print_hex = true;
        break;
      default:
        usage(av[0]);
    }
  }

  fd = open(port, O_RDWR | O_SYNC | O_NOCTTY);

  if (fd == -1) {
    perror(port);
    exit(2);
  }

  set_portopt(fd);
  pn532_init(fd);

  while (1) {
    if (read_passive(fd) == ESP_OK)
      usleep(100 * 1000);

    usleep(100 * 1000);
  }

  close(fd);

  return 0;
}

